package org.dyndns.opendemogroup.optimizer.operations;

import java.util.Random;

import org.dyndns.opendemogroup.optimizer.ArrayUtil;
import org.dyndns.opendemogroup.optimizer.IOperation;
import org.dyndns.opendemogroup.optimizer.IOptimizationProblem;
import org.dyndns.opendemogroup.optimizer.Member;

/**
 * Based on slide 16 of <code>evolution strategies.pdf</code>.
 */
public class MultiSigmaMutator implements IOperation
{

	private final double baseAlpha;

	private final double inverseBaseAlpha;

	public MultiSigmaMutator ( double baseAlpha )
	{
		this.baseAlpha = baseAlpha;
		this.inverseBaseAlpha = 1.0 / baseAlpha;
	}

	/**
	 * @see IOperation#perform(java.util.Random, IOptimizationProblem, Member,
	 *      Member, Member, Member)
	 */
	public void perform ( Random randomSource, IOptimizationProblem problem,
			Member parentOne, Member parentTwo, Member childOne, Member childTwo )
	{
		final int memberSize = parentOne.getMemberSize ( );
		final int partSize = memberSize / 3;
		final int sigmaOffset = partSize;
		final int alphaOffset = partSize * 2;

		mutate ( randomSource, memberSize, partSize, sigmaOffset, alphaOffset,
			parentOne.genome, childOne.genome );
		mutate ( randomSource, memberSize, partSize, sigmaOffset, alphaOffset,
			parentTwo.genome, childTwo.genome );
	}

	void mutate ( Random randomSource, final int memberSize,
			final int partSize, final int sigmaOffset, final int alphaOffset,
			final double[] parent, final double[] child )
	{
		ArrayUtil.copy ( parent, child, 0, memberSize - 1 );

		// { mutate the alpha
		for ( int i = 0, a = alphaOffset; i < partSize; i++, a++ )
		{
			final double e = randomSource.nextDouble ( );
			if ( e < 0.5 )
			{
				child[a] = baseAlpha;
			}
			else
			{
				child[a] = inverseBaseAlpha;
			}
		}
		// }

		// { mutate the sigma
		for ( int i = 0, s = sigmaOffset, a = alphaOffset; i < partSize; i++, s++, a++ )
		{
			child[s] *= child[a];
		}
		// }

		// { mutate the paramaters
		for ( int i = 0, s = sigmaOffset; i < partSize; i++, s++ )
		{
			final double Ni = randomSource.nextGaussian ( );
			child[i] += child[s] * Ni;
		}
		// }
	}
}
